﻿
Imports System.IO
Imports System.Net
Imports std

Public Class xmlcontrol

    Private _output As Stream = Nothing
    'Private _writer As StreamWriter = Nothing
    'Private _reader As StreamReader = Nothing
    'Private _writer As TextWriter = Nothing
    'Private _reader As TextReader = Nothing

    Private _father As xmlcontrol = Nothing
    Private _children As list(Of xmlcontrol) = Nothing

    Private _depth As Int32
    Private _tag As String = Nothing
    Private _text As Object = Nothing
    Private _attributes As map(Of String, Object) = Nothing

    Private _tabs As String = Nothing

    Public Enum textEncodingEnum
        htmlencoding
        none
    End Enum

    Public Enum attributeEncodingEnum
        htmlencoding
        none
    End Enum

    Protected Overridable Function textEncoding() As textEncodingEnum
        Return textEncodingEnum.htmlencoding
    End Function

    Protected Overridable Function attributeEncoding() As attributeEncodingEnum
        Return attributeEncodingEnum.htmlencoding
    End Function

    Protected Overridable Function isonelineControl() As Boolean
        Return (text() Is Nothing OrElse isemptystring(text().ToString())) _
                AndAlso (children() Is Nothing OrElse children().empty())
    End Function

    Public Property output() As MemoryStream
        Get
            Return _output
        End Get
        Set(ByVal value As MemoryStream)
            _output = value
            '_writer = New StreamWriter(_output, encoding.utf8)
            '_reader = New StreamReader(_output, encoding.utf8)
            assert(Not _children Is Nothing, "_children is nothing when set output.")
            Dim it As list(Of xmlcontrol).iterator = Nothing
            it = _children.begin()
            While it <> _children.end()
                With +it
                    .output = value
                End With
                it += 1
            End While
        End Set
    End Property

    'Public Function writer() As TextWriter
    '    assert(Not _writer Is Nothing, "_writer has not been initialized.")
    '    Return _writer
    'End Function

    'Public Sub setWriter(ByVal writer As TextWriter)
    '    _writer = writer
    '    redirectWriter = True
    'End Sub

    Public Shared Function tagleading() As String
        Return character.leftAngleBracket
    End Function

    Public Shared Function tagfinal() As String
        Return character.rightAngleBracket
    End Function

    Public Shared Function endtagmark() As String
        Return character.leftSlash
    End Function

    Public Shared Function attributeSeperator() As String
        Return character.equalSign
    End Function

    Public Shared Function valueleading() As String
        Return character.quoteMark
    End Function

    Public Shared Function valuefinal() As String
        Return character.quoteMark
    End Function

    Protected Overridable Function _tagleading() As String
        Return tagleading()
    End Function

    Protected Overridable Function _tagfinal() As String
        If isonelineControl() Then
            Return character.blank + xmlcontrol.endtagmark() + xmlcontrol.tagfinal()
        Else
            Return tagfinal()
        End If
    End Function

    Protected Overridable Function _endtagmark() As String
        Return endtagmark()
    End Function

    Protected Overridable Function _attributeSeperator() As String
        Return attributeSeperator()
    End Function

    Protected Overridable Function _valueleading() As String
        Return valueleading()
    End Function

    Protected Overridable Function _valuefinal() As String
        Return valuefinal()
    End Function

    Public Function depth() As Int32
        Return _depth
    End Function

    Public Property tag() As String
        Get
            Return _tag
        End Get
        Set(ByVal value As String)
            debugassert(Not strcont(value, character.blank), "should not have blank in tag.")
            copy(_tag, value)
        End Set
    End Property

    Public Overridable Property text() As Object
        Get
            Return _text
        End Get
        Set(ByVal value As Object)
            copy(_text, value)
        End Set
    End Property

    Public Function attributes() As map(Of String, Object)
        Return _attributes
    End Function

    Public Function father() As xmlcontrol
        Return _father
    End Function

    Public Function children() As list(Of xmlcontrol)
        assert(Not _children Is Nothing, "_children has not been initialized.")
        Return _children
    End Function

    Public Overridable Sub addchild(ByVal ctrl As xmlcontrol)
        debugassert(Not Object.ReferenceEquals(ctrl, Me), "should not add a control to its own children list.")
        children().push_back(ctrl)
        ctrl.setfather(Me)
        ctrl.output() = output()
    End Sub

    Protected Overridable Sub setfather(ByVal c As xmlcontrol)
        _father = c
    End Sub

    Protected Overridable Sub set_childdepth(ByRef depth As Int32)
        depth = _depth + 1
    End Sub

    Protected Property attribute(ByVal name As String) As Object
        Get
            Dim it As map(Of String, Object).iterator = Nothing
            it = _attributes.find(name)
            If it <> _attributes.end Then
                Return (+it).second
            Else
                Return Nothing
            End If
        End Get
        Set(ByVal value As Object)
            _attributes.insert(name, value)
        End Set
    End Property

    Protected Sub write(ByVal s As String)
        'assert(Not _writer Is Nothing, "cannot get response stream from HttpListenerContext")
        'If Not isemptystring(s) Then
        '    _writer.Write(s)
        '    _writer.Flush()
        'End If
        assert(Not _output Is Nothing, "cannot write when there is no output stream.")
        assert(_output.CanWrite, "output stream cannot write.")
        If Not isemptystring(s) Then
            Dim bs() As Byte
            bs = encoding.utf8().GetBytes(s)
            If Not bs Is Nothing AndAlso bs.Length() > 0 Then
                _output.Write(bs, 0, bs.Length())
                _output.Flush()
            End If
        End If
    End Sub

    Protected Sub writeline(ByVal s As String)
        write(s + newline.incode)
    End Sub

    Protected Function renderTabs(ByVal compress As Boolean) As Boolean
        If Not compress Then
            If isemptystring(_tabs) Then
                Dim i As Int32
                For i = 0 To _depth - 1
                    _tabs += character.tab
                Next
            End If
            write(_tabs)
        End If

        Return True
    End Function

    Private Function renderAttribute(ByVal compress As Boolean, ByVal name As String) As Boolean
        Dim it As map(Of String, Object).iterator = Nothing
        it = attributes.find(name)
        If it <> attributes.end() Then
            Return renderAttribute(compress, name, (+it).second)
        Else
            Return True
        End If
    End Function

    Protected Overridable Function renderAttribute(ByVal compress As Boolean, _
                                                   ByVal name As String, ByVal value As Object) As Boolean
        write(character.blank)
        If Not isemptystring(name) Then
            write(name)
        End If
        If Not value Is Nothing Then
            Dim s As String = Nothing
            s = Convert.ToString(value)
            If attributeEncoding() = attributeEncodingEnum.htmlencoding Then
                htmlUtils.escape(s)
            End If

            write(_attributeSeperator() + _valueleading() + s + _valuefinal())
        End If

        Return True
    End Function

    Private Function inorders(ByVal name As String, ByVal ParamArray orders() As String) As Boolean
        If orders Is Nothing OrElse orders.Length() = 0 Then
            Return False
        Else
            Dim i As Int64
            For i = 0 To orders.Length() - 1
                If strsame(name, orders(i), True) Then
                    Return True
                End If
            Next

            Return False
        End If
    End Function

    Protected Function renderAttributesImpl(ByVal compress As Boolean, ByVal ParamArray orders() As String) As Boolean
        If Not orders Is Nothing AndAlso orders.Length() > 0 Then
            Dim i As Int64
            For i = 0 To orders.Length() - 1
                If Not renderAttribute(compress, orders(i)) Then
                    Return False
                End If
            Next
        End If

        Dim it As map(Of String, Object).iterator = Nothing
        it = attributes().begin()
        While it <> attributes().end()
            If Not inorders((+it).first, orders) Then
                If Not renderAttribute(compress, (+it).first, (+it).second) Then
                    Return False
                End If
            End If
            it += 1
        End While

        Return True
    End Function

    Protected Overridable Function renderAttributes(ByVal compress As Boolean) As Boolean
        Return renderAttributesImpl(compress)
    End Function

    Protected Overridable Function renderBeginTag(ByVal compress As Boolean) As Boolean
        If isemptystring(tag()) Then
            Return True
        End If

        If Not renderTabs(compress) Then
            Return False
        End If

        write(_tagleading() + _tag)
        If Not renderAttributes(compress) Then
            Return False
        End If
        If Not compress Then
            writeline(_tagfinal())
        Else
            write(_tagfinal())
        End If

        Return True
    End Function

    Protected Overridable Function renderEndTag(ByVal compress As Boolean) As Boolean
        If isonelineControl() OrElse isemptystring(tag()) Then
            Return True
        Else
            If Not renderTabs(compress) Then
                Return False
            End If

            Dim str As String = Nothing
            str = _tagleading() + _endtagmark() + _tag + _tagfinal()
            If Not compress Then
                writeline(str)
            Else
                write(str)
            End If
            Return True
        End If
    End Function

#If RETIRED Then
    'NEVER RENDER TAB / NEWLINE BEFORE OR AFTER TEXT
    Protected Sub renderTextTab()
        write(character.tab)
    End Sub
#End If

    Protected Function renderText(ByVal compress As Boolean) As Boolean
        If text() Is Nothing Then
            Return True
        End If

#If RETIRED Then
        If Not renderTabs(compress) Then
            Return False
        End If
#End If

        Dim s As String = Nothing
        s = Convert.ToString(text())
        If Not isemptystring(s) Then
            If textEncoding() = textEncodingEnum.htmlencoding Then
                htmlUtils.escape(s)
            End If

#If RETIRED Then
            If Not compress Then
                renderTextTab()
                writeline(s)
            Else
                write(s)
            End If
#End If
            write(s)
        End If
        Return True
    End Function

    Protected Overridable Function renderChildren(ByVal compress As Boolean) As Boolean
        If children() Is Nothing OrElse children().size() = 0 Then
            Return True
        End If

        Dim it As list(Of xmlcontrol).iterator = Nothing
        it = children().begin
        While it <> children().end()
            Dim ctrl As xmlcontrol = Nothing
            ctrl = (+it)
            set_childdepth(ctrl._depth)
            'If redirectWriter Then
            '    ctrl.setWriter(_writer)
            'End If
            If Not ctrl.render(compress) Then
                raiseError("render xmlcontrol failed, xmlcontrol " + ctrl.GetType().FullName() _
                           , errorHandle.errorType.exclamation)
                Return False
            End If
            it += 1
        End While

        Return True
    End Function

    Protected Function preRenderCheck() As Boolean
        If output Is Nothing Then
            raiseError("cannot get output instance", errorHandle.errorType.exclamation)
            Return False
        ElseIf Not output.CanWrite Then
            raiseError("output stream cannot write.", errorHandle.errorType.exclamation)
            Return False
        Else
            Return True
        End If
    End Function

    Public Shared Function shouldCompress() As Boolean
        'change debugmode to debugbuild, so can get real output when in release build
        Return Not isdebugbuild()
    End Function

    Public Function render() As Boolean
        Return render(shouldCompress())
    End Function

    'for internal overrides use only
    Protected Overridable Function render(ByVal compress As Boolean) As Boolean
        If preRenderCheck() Then
            If Not renderBeginTag(compress) Then
                raiseError("render begin tag failed, xmlcontrol " + Me.GetType.FullName() + ", tag " + _tag _
                           , errorHandle.errorType.exclamation)
                Return False
            End If

            If Not renderText(compress) Then
                raiseError("render text failed, xmlcontrol " + Me.GetType.FullName() + ", text " + _text.ToString() _
                           , errorHandle.errorType.exclamation)
                Return False
            End If

            If Not renderChildren(compress) Then
                raiseError("cannot render children controls, xmlcontrol " + Me.GetType.FullName() _
                            , errorHandle.errorType.exclamation)
                Return False
            End If

            If Not renderEndTag(compress) Then
                raiseError("render end tag failed, xmlcontrol " + Me.GetType.FullName() + ", tag " + _tag _
                           , errorHandle.errorType.exclamation)
                Return False
            End If

            Return True
        Else
            Return False
        End If
    End Function

    Public Overridable Sub clear()
        setfather(Nothing)
        children().clear()
        _depth = 0
        _tag = Nothing
        _text = Nothing
        attributes().clear()
        _tabs = Nothing
        _output = Nothing
    End Sub

    Public Sub New(Optional ByVal buff As Stream = Nothing, Optional ByVal tag As String = Nothing)
        _children = New list(Of xmlcontrol)()
        _attributes = New map(Of String, Object)()
        output = buff
        Me.tag() = tag
    End Sub

    'Public Overrides Function ToString() As String
    '    Dim ms As MemoryStream = Nothing
    '    If output Is Nothing Then
    '        ms = New MemoryStream
    '        Dim writer As StreamWriter = Nothing
    '        writer = New StreamWriter(ms, encoding.utf8)
    '        setWriter(writer)
    '    Else
    '        ms = output
    '    End If
    '    If Not render() Then
    '        Return Nothing
    '    Else
    '        Dim sr As StreamReader = Nothing
    '        sr = New StreamReader(ms)
    '        Return sr.ReadToEnd()
    '    End If
    'End Function

    Public Overloads Function ToString(ByVal compress As Boolean) As String
        Dim outputBackup As Stream = Nothing
        outputBackup = output
        output = New MemoryStream()
        Dim rtn As String = Nothing
        If Not render(compress) Then
            closestream(output)
            rtn = Nothing
        Else
            Dim reader As StreamReader = Nothing
            reader = New StreamReader(output)
            reader.BaseStream.Position = 0
            rtn = reader.ReadToEnd()
            closestream(output)
            closereader(reader)
        End If
        'when set output, write & reader will be initialized
        output = outputBackup
        Return rtn
    End Function

    Public NotOverridable Overrides Function ToString() As String
        Return ToString(shouldCompress())
    End Function

#If NOTFINISHED Then
    Public Function replace(ByVal dest As xmlcontrol) As Boolean

    End Function

    Public Function replaceBy(ByVal src As xmlcontrol) As Boolean

    End Function
#End If
End Class
